﻿package org.hsl.haxe;
/**
 * A common implementation of the ISlotList interface.
 * 
 * The common slot list uses a doubly linked list structure internally.
 */
class CommonSlotList<D> implements ISlotList<D> {
	public var hasSlots(getHasSlots, null):Bool;
	private var sentinel:Sentinel<D>;
	/**
	 * Creates a new common slot list. The slot list will connect itself using the passed method.
	 */
	public function new(connectToSlotListMethod:ISignalerSlotListBridge<D> -> Void):Void {
		sentinel = new Sentinel<D>();
		// Connect using the passed method.
		connectToSlotListMethod(new SignalerCommonSlotListBridge<D>(sentinel));
	}
	/**
	 * Adds a linked slot at the end of the doubly linked list structure.
	 */
	private inline function add(value:AbstractLinkedSlot<D>):ISlot<D> {
		sentinel.insertBefore(value);
		return value;
	}
	public function addNiladic(method:Void -> Void):ISlot<D> {
		return add(new NiladicSlot<D>(method));
	}
	public function addRegular(method:Signal<D> -> Void):ISlot<D> {
		return add(new RegularSlot<D>(method));
	}
	public function addSimple(method:D -> Void):ISlot<D> {
		return add(new SimpleSlot<D>(method));
	}
	private function getHasSlots():Bool {
		return sentinel.determineIsPopulated();
	}
	/**
	 * Removes a slot equal to the passed value from the doubly linked list structure. Returns true if a slot equal to the passed
	 * value is found and destroyed; false otherwise.
	 */
	private inline function remove(value:AbstractLinkedSlot<D>):Bool {
		return sentinel.remove(value);
	}
	public function removeNiladic(method:Void -> Void):Bool {
		return remove(new NiladicSlot<D>(method));
	}
	public function removeRegular(method:Signal<D> -> Void):Bool {
		return remove(new RegularSlot<D>(method));
	}
	public function removeSimple(method:D -> Void):Bool {
		return remove(new SimpleSlot<D>(method));
	}
	private function toString():String {
		return "[ISlotList]";
	}
}
/**
 * The signaler-slot list bridge used by the common slot list.
 */
class SignalerCommonSlotListBridge<D> implements ISignalerSlotListBridge<D> {
	private var sentinel:Sentinel<D>;
	/**
	 * Creates a new signaler-common slot list bridge.
	 */
	public function new(sentinel:Sentinel<D>):Void {
		this.sentinel = sentinel;
	}
	public function callSlots(signal:Signal<D>):Void {
		sentinel.callSlots(signal);
	}
	private function toString():String {
		return "[ISignalerSlotListBridge]";
	}
}
/**
 * A slot that can be used for Void -> Void methods.
 */
class NiladicSlot<D> extends AbstractLinkedSlot<D> {
	private var method:Void -> Void;
	/**
	 * Creates a new niladic slot.
	 */
	public function new(method:Void -> Void):Void {
		super();
		this.method = method;
	}
	public override function call(signal:Signal<D>):Void {
		method();
	}
	private override function determineEquality(slot:ISlot<D>):Bool {
		return Std.is(slot, NiladicSlot) && Reflect.compareMethods((untyped slot).method, method);
	}
}
/**
 * A slot that can be used for Signal<D> -> Void methods.
 */
class RegularSlot<D> extends AbstractLinkedSlot<D> {
	private var method:Signal<D> -> Void;
	/**
	 * Creates a new regular slot.
	 */
	public function new(method:Signal<D> -> Void):Void {
		super();
		this.method = method;
	}
	public override function call(signal:Signal<D>):Void {
		method(signal);
	}
	private override function determineEquality(slot:ISlot<D>):Bool {
		return Std.is(slot, RegularSlot) && Reflect.compareMethods((untyped slot).method, method);
	}
}
/**
 * A slot that can be used for D -> Void methods.
 */
class SimpleSlot<D> extends AbstractLinkedSlot<D> {
	private var method:D -> Void;
	/**
	 * Creates a new simple slot.
	 */
	public function new(method:D -> Void):Void {
		super();
		this.method = method;
	}
	public override function call(signal:Signal<D>):Void {
		method(signal.data);
	}
	private override function determineEquality(slot:ISlot<D>):Bool {
		return Std.is(slot, SimpleSlot) && Reflect.compareMethods((untyped slot).method, method);
	}
}
/**
 * A doubly linked slot. The slot has a reference to the next and the previous slot in the list.
 */
class AbstractLinkedSlot<D> implements ISlot<D> {
	private var destroyed(default, null):Bool;
	private var next:AbstractLinkedSlot<D>;
	private var previous:AbstractLinkedSlot<D>;
	public function new():Void {
		destroyed = false;
	}
	public function call(signal:Signal<D>):Void {
	}
	private function determineEquality(slot:ISlot<D>):Bool {
		return false;
	}
	public function destroy():Void {
		// If this slot has already been destroyed, don't destroy it again.
		if (destroyed) {
			return;
		}
		previous.next = next;
		next.previous = previous;
		destroyed = true;
	}
	private function toString():String {
		return "[ISlot]";
	}
}
/**
 * A sentinel "slot". The sentinel is not a real slot, rather it is the value before the first node and after the last node. It
 * contains logic that helps the common slot list using the linked list structure.
 */
class Sentinel<D> extends AbstractLinkedSlot<D> {
	public function new():Void {
		super();
		next = previous = this;
	}
	public inline function callSlots(signal:Signal<D>):Void {
		var node:AbstractLinkedSlot<D> = next;
		while (node != this) {
			node.call(signal);
			node = node.next;
		}
	}
	/**
	 * Determines whether this sentinel is connected to actual slots, or not.
	 */
	public inline function determineIsPopulated():Bool {
		return next != this;
	}
	/**
	 * Inserts a slot between the sentinel and the previous slot.
	 */
	public inline function insertBefore(value:AbstractLinkedSlot<D>):Void {
		value.next = this;
		value.previous = previous;
		previous = previous.next = value;
	}
	/**
	 * Removes a slot connected to the sentinel.
	 * 
	 * The sentinel will look for a slot equal to the passed value directy or indirectly connected to it, and destroy it. Returns
	 * true if a slot equal to the passed value is found and destroyed; false otherwise.
	 */
	public inline function remove(value:AbstractLinkedSlot<D>):Bool {
		var node:AbstractLinkedSlot<D> = next;
		var result:Bool = false;
		while (node != this) {
			if (node.determineEquality(value)) {
				node.destroy();
				result = true;
				break;
			}
			node = node.next;
		}
		return result;
	}
}